﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Data.SqlClient;
using BEELab.Data;
using System.Data;

namespace BEELab.Logic
{
    public static class UserManagement
    {
        /// <summary>
        /// Creates new user, insert all its details to DB
        /// </summary>
        /// <param name="FirstName">First Name</param>
        /// <param name="LastName">Last Name</param>
        /// <param name="Email">Email</param>
        /// <param name="University">University</param>
        /// <param name="isAdmin">Administrator= True, Regular user = False</param>
        /// <param name="Password">Password</param>
        /// <returns>If users created successfully - returnes it's ID number on DB. Otherwise returns 0</returns>
        public static int CreateUser(string FirstName, string LastName, string Email, string University, Boolean isAdmin, string Password)
        {
            if (EmailRegistered(Email))
                return 0;

            string salt = CryptographyHelper.GenerateRandomString(8);
            string hash = CryptographyHelper.Hash(Password + salt);

            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO Users (FirstName, LastName, Email, University, isAdmin, Salt, Hash) VALUES (@FirstName, @LastName, @Email, @University, @isAdmin, @Salt, @Hash); SELECT SCOPE_IDENTITY();";
            command.Parameters.Add(new SqlParameter("@FirstName", FirstName));
            command.Parameters.Add(new SqlParameter("@LastName", LastName));
            command.Parameters.Add(new SqlParameter("@Email", Email));
            command.Parameters.Add(new SqlParameter("@University", University));
            command.Parameters.Add(new SqlParameter("@isAdmin", isAdmin));
            command.Parameters.Add(new SqlParameter("@Salt", salt));
            command.Parameters.Add(new SqlParameter("@Hash", hash));

            try
            {
                int id = int.Parse(DBHelper.ExecuteScalar(command));

                command = new SqlCommand();
                command.CommandText = "SELECT ID FROM Users";
                DataTable dt = DBHelper.ExecuteQuery(command);

                if (dt.Rows.Count == 1 && id != 0)
                {
                    SetAdmin(Email, true);
                }

                return id;
            }
            catch (Exception)
            {
                return 0;
            }
        }

        /// <summary>
        /// Updates existing user's detailes on DB 
        /// </summary>
        /// <param name="ID">ID</param>
        /// <param name="FirstName">First Name</param>
        /// <param name="LastName">Last Name</param>
        /// <param name="Email">Email</param>
        /// <param name="University">University</param>
        /// <returns>True id update succeeded, False otherwise</returns>
        public static Boolean UpdateUser(int ID, string FirstName, string LastName, string Email, string University)
        {
            //If user doesn't exists
            if (!EmailRegistered(Email))
                return false;

            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Users SET FirstName=@FirstName, LastName=@LastName, Email=@Email, University=@University WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@FirstName", FirstName));
            command.Parameters.Add(new SqlParameter("@LastName", LastName));
            command.Parameters.Add(new SqlParameter("@Email", Email));
            command.Parameters.Add(new SqlParameter("@University", University));
            command.Parameters.Add(new SqlParameter("@ID", ID));

            int res = DBHelper.ExecuteNonQuery(command);

            //Update succeeded
            if (res == 1)
                return true;

            //Update failed
            return false;
        }

        /// <summary>
        /// Gets email and password, checks on DB if password is correct.
        /// </summary>
        /// <param name="Email">User's Email</param>
        /// <param name="Password">User's current password</param>
        /// <returns>True- if there is a match, false otherwise</returns>
        public static Boolean AuthenticateUser(string Email, string Password)
        {
            string salt;
            string hash;

            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT Salt, Hash FROM Users WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Email", Email));
            DataTable dt = DBHelper.ExecuteQuery(command);

            //If user's wasn't found on DB
            if (dt == null || dt.Rows.Count != 1)
                return false;

            //else - if user found, compare passwords to see if correct
            salt = (string)dt.Rows[0]["Salt"];
            hash = (string)dt.Rows[0]["Hash"];

            //calculate hash and compare
            if (CryptographyHelper.Hash(Password + salt) == hash)
                return true;

            return false;
        }


        /// <summary>
        /// Gets email, old and new passwords, checks in DB that old password is correct.
        /// If password is correct - updates new password on DB and return true, else return false.
        /// </summary>
        /// <param name="Email">User's E-mail address</param>
        /// <param name="OldPassword">User's current password</param>
        /// <param name="NewPassword">User's new password</param>
        /// <returns>True - if update succedded, false - otherwise</returns>
        public static Boolean ChangePassword(string Email, string OldPassword, string NewPassword)
        {
            //Check match between old password to user's email
            if (!AuthenticateUser(Email, OldPassword))
                return false;
            
            //If we found a match, update user's new password on DB
            string salt = CryptographyHelper.GenerateRandomString(8);
            string hash = CryptographyHelper.Hash(NewPassword + salt);

            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Users SET Hash=@Hash, Salt=@Salt WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Hash", hash));
            command.Parameters.Add(new SqlParameter("@Salt", salt));
            command.Parameters.Add(new SqlParameter("@Email", Email));

            int res = DBHelper.ExecuteNonQuery(command);

            //Update succeeded
            if (res == 1)
                return true;

            //Update failed
            return false;
        }

        /// <summary>
        /// Gets an email address, check if user is admin or regular user
        /// </summary>
        /// <param name="Email">User's Email</param>
        /// <returns>True if admin, False otherwise</returns>
        public static Boolean IsAdmin(string Email)
        {
            //Check if user exists on DB
            if (!EmailRegistered(Email))
                return false;

            //Check if user is admin on DB
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT isAdmin FROM Users WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Email", Email));
            string result = DBHelper.ExecuteScalar(command);

            //If admin
            if (result == "True")
                return true;

            //else
            return false;
        }

        /// <summary>
        /// Changes a password of a specific user in the system, by one of the admin users and not by
        /// the user itself.
        /// </summary>
        /// <param name="AdminEmail">Admin's email</param>
        /// <param name="AdminPassword">Admin's password</param>
        /// <param name="UserEmail">Requested user's email</param>
        /// <param name="NewPassword">New password</param>
        /// <returns>True- if password update succeeded, False - otherwise</returns>
        public static Boolean ChangePassword(string AdminEmail, string AdminPassword, string UserEmail, string NewPassword)
        {
            //Check if user exists on DB
            if (!EmailRegistered(UserEmail))
                return false;
            
            //Check that the person that changes the password exists on DB,
            //inserted his own correct password, and administrator
            if (!AuthenticateUser(AdminEmail, AdminPassword) || !IsAdmin(AdminEmail))
                return false;

            //if al of the above correct - change user's password on DB
            string salt = CryptographyHelper.GenerateRandomString(8);
            string hash = CryptographyHelper.Hash(NewPassword + salt);

            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Users SET Hash=@Hash, Salt=@Salt WHERE Email=@UserEmail";
            command.Parameters.Add(new SqlParameter("@Hash", hash));
            command.Parameters.Add(new SqlParameter("@Salt", salt));
            command.Parameters.Add(new SqlParameter("@UserEmail", UserEmail));

            int res = DBHelper.ExecuteNonQuery(command);

            //Update succeeded
            if (res == 1)
                return true;

            //Update failed
            return false;
        }

        /// <summary>
        /// Gets user's ID, delete user from DB
        /// </summary>
        /// <param name="ID">User's ID as appear on DB</param>
        /// <returns>True if removed, False otherwise</returns>
        public static Boolean DeleteUser(int ID)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "DELETE FROM Users WHERE ID=@ID";
            command.Parameters.Add(new SqlParameter("@ID", ID));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res > 0)
                return true;

            return false;
        }

        /// <summary>
        /// Changes an existing user premitions on DB
        /// </summary>
        /// <param name="Email">User's Email</param>
        /// <param name="isAdmin">Boolean value - if true, the function will make the user administrator.
        /// if false - the function we turn it to regular user</param>
        /// <returns>True - if update secceeded, false otherwise</returns>
        public static Boolean SetAdmin(string Email, Boolean isAdmin)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Users SET isAdmin=@isAdmin WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@isAdmin", isAdmin));
            command.Parameters.Add(new SqlParameter("@Email", Email));

            int res = DBHelper.ExecuteNonQuery(command);

            if (res == 1)
                return true;

            return false;
        }

        /// <summary>
        /// Gets an email address, checks if exists on DB
        /// </summary>
        /// <param name="Email">User's email address</param>
        /// <returns>True if user exists on DB, False otherwise</returns>
        public static Boolean EmailRegistered(string Email)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT Count(*) FROM Users WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Email", Email));
            int result = int.Parse(DBHelper.ExecuteScalar(command));

            return result > 0;
        }

        /// <summary>
        /// Gets user's email, return it's ID number as appears on DB
        /// </summary>
        /// <param name="Email">User's email</param>
        /// <returns>User's ID as appears on DB</returns>
        public static int GetUserID(string Email)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT ID FROM Users WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Email", Email));
            string res = DBHelper.ExecuteScalar(command);

            if (res == null)
                return 0;

            return int.Parse(res);
        }

        /// <summary>
        /// Gets user's Token and returns user's Email
        /// </summary>
        /// <param name="Token">User's Token</param>
        /// <returns>User's Email</returns>
        public static string EmailFromToken(string Token)
        {
            SqlCommand command = new SqlCommand();
            command.CommandText = "SELECT Email FROM ResetTokens WHERE Token=@Token";
            command.Parameters.Add(new SqlParameter("@Token", Token));
            return DBHelper.ExecuteScalar(command);
        }

        /// <summary>
        /// Gets an email address, creates rendom string to use as token, and insert token + email to DB.
        /// </summary>
        /// <param name="Email">User's Email</param>
        /// <returns>User's token string</returns>
        public static string CreateToken(string Email)
        {
            string token = BEELab.Data.CryptographyHelper.GenerateRandomString(64);

            SqlCommand command = new SqlCommand();
            command.CommandText = "INSERT INTO ResetTokens (Token, Email) VALUES (@Token, @Email)";
            command.Parameters.Add(new SqlParameter("@Token", token));
            command.Parameters.Add(new SqlParameter("@Email", Email));
            BEELab.Data.DBHelper.ExecuteNonQuery(command);

            return token;
        }

        /// <summary>
        /// Gets a string - "Token" (that was created for the user randomly) and a new password.
        /// According the token retrieves user's email address, and reset user's password.
        /// </summary>
        /// <param name="Token">Random string that was creates to the user on DB</param>
        /// <param name="NewPassword">User's desired new password</param>
        /// <returns>True if updated password, False otherwise</returns>
        public static Boolean ChangePassword(string Token, string NewPassword)
        {
            //Retrieves email from DB according to Token
            string Email = EmailFromToken(Token);
            if (Email == null || Email.Length == 0)
                return false;

            //If found - updated new password on DB
            string salt = CryptographyHelper.GenerateRandomString(8);
            string hash = CryptographyHelper.Hash(NewPassword + salt);

            SqlCommand command = new SqlCommand();
            command.CommandText = "UPDATE Users SET Hash=@Hash, Salt=@Salt WHERE Email=@Email";
            command.Parameters.Add(new SqlParameter("@Hash", hash));
            command.Parameters.Add(new SqlParameter("@Salt", salt));
            command.Parameters.Add(new SqlParameter("@Email", Email));

            int res = DBHelper.ExecuteNonQuery(command);

            //Update succeeded
            if (res == 1)
            {
                command = new SqlCommand();
                command.CommandText = "DELETE FROM ResetTokens WHERE Token=@Token";
                command.Parameters.Add(new SqlParameter("@Token", Token));
                DBHelper.ExecuteNonQuery(command);

                return true;
            }

            //Update failed
            return false;
        }
    }
}